#include "linked_list.h"
#include "server.h"
#include "user.h"
#include <sys/stat.h>

#define FILENAME "bigfile.avi"

extern ListNode * userList;

typedef enum {
    OPERATION_DOWNLOAD_FILE = 1,
    OPERATION_UPLOAD_FILE = 2,
} operation_t;

static user_info_t * findUser(int sockfd)
{
    user_info_t * user = NULL;
    ListNode * p = userList;
    //查找用户节点
    while(p != NULL) {
        if(sockfd == ((user_info_t*)p->val)->transferfd) {
            printf("----------found user.\n");
            user = (user_info_t*)p->val;
            break;
        }
        p = p->next;
    }
    return user;
}

//sendn函数可以发送确定的字节数
int sendn(int sockfd, const void * buff, int len)
{
    int left = len;
    const char* pbuf = buff;
    int ret = -1;
    while(left > 0) {
        ret = send(sockfd, pbuf, left, 0);
        if(ret < 0) {
            perror("send");
            return -1;
        }
        left -= ret;
        pbuf += ret;
    }
    return len - left;
}


void handleReadEvent(int sockfd, int epfd)//处理读事件
{
    printf("handle read event.\n");
    //查找用户节点
    user_info_t * user = findUser(sockfd);
    /* printf("user: %p\n", user); */
    assert(user != NULL);
    if(user->recvCnt == 0) {
        printf(">> first recv.\n");
        //第一次接收客户端的数据，此时是标志位flag
        int flag = 0;
        int ret = recv(sockfd, &flag, sizeof(flag), 0);
        if(ret == 0) {
            printf(">> recv ret is zero.\n") ;
            goto label_exit_read_event;
        }
        //简化设计，客户端发1过来时，表示下载,
        //服务器就开始准备发送文件的操作
        if(flag == OPERATION_DOWNLOAD_FILE) {
            user->fileReadfd = open(FILENAME, O_RDWR);
            struct stat st;
            memset(&st, 0, sizeof(st));
            fstat(user->fileReadfd, &st);
            user->filesize = st.st_size;
            //接下来即将进入发送数据的流程，即要触发EPOLLOUT事件
            //那就需要让sockfd监听EPOLLOUT写事件
            epollAddWriteEvent(epfd, sockfd);
        } else if(flag == OPERATION_UPLOAD_FILE) {
            //客户端发送2过来，表示上传，
            //服务器就开始准备接收文件的操作
            if(user) {
                user->fileWritefd = open("upload.avi", O_CREAT | O_RDWR);
            }
        }
        user->recvCnt++; //++之后，就不是第一次接收client的数据了
        return;
    } else {
        //else部分表示：不是第一次接收client的数据了,
        //那接下来就是接收文件了
        int sz = sizeof(user->recvBuff);
        //每次接收文件的一部分数据,并写入服务器本地文件upload.avi
        int ret = recv(sockfd, user->recvBuff, sz, 0);
        if(ret == 0) { //连接断开时
            printf(">> recv ret is zero.\n") ;
            goto label_exit_read_event;   
        } else if (ret > 0) {
            //写入服务器本地文件upload.avi
            ret = write(user->fileWritefd, user->recvBuff, ret);
            user->hasRecvSize += ret;
            printf(">> conn %d: recv %d bytes.\n", sockfd, user->hasRecvSize);
        }
        return;
    }   

label_exit_read_event:
    epollDelReadEvent(epfd, sockfd);
    printf("conn %d has closed.\n", sockfd);
    close(sockfd);
}

void handleWriteEvent(int sockfd, int epfd)//处理写事件
{
    printf("handle write event. sockfd:%d\n", sockfd);
    //查找用户节点
    user_info_t * user = findUser(sockfd);
    if(user) {
        //清空用户的发送缓冲区
        int sz = sizeof(user->sendBuff);
        memset(user->sendBuff, 0, sz);
        //读取文件中的数据
        int ret = read(user->fileReadfd, user->sendBuff, sz);
        if(ret == 0) {//文件发送完毕
                      //不再需要监听EPOLLOUT写事件了
                      //所以清除写事件的监听
            epollClearWriteEvent(epfd, sockfd);
            return;
        }
        //每次读取一部分数据，再发送
        ret = sendn(sockfd, user->sendBuff, ret);
        if(ret < 0) {
            return;
        }
        user->hasSendSize += ret;
        printf("conn %d: send %d bytes.\n", sockfd, user->hasSendSize);
    }
    usleep(50000);
}

#if 0/*{{{*/
int transferFile(int peerfd)
{
    //读取本地文件
    int fd = open(FILENAME, O_RDWR);
    ERROR_CHECK(fd, -1, "open");
    //获取文件的长度
    struct stat st;
    memset(&st, 0, sizeof(st));
    fstat(fd, &st);
    char buff[100] = {0};
    int filelength = st.st_size;
    printf("filelength: %d\n", filelength);

    //进行发送操作
    //1. 发送文件名
    train_t t;
    memset(&t, 0, sizeof(t));
    t.len = strlen(FILENAME);
    strcpy(t.buf, FILENAME);
    sendn(peerfd, &t, 4 + t.len);
    
    //2. 再发送文件内容
    //2.1 发送文件的长度
    sendn(peerfd, &filelength, sizeof(filelength));

    //服务器这一边采用的是零拷贝的技术
    //当mmap映射成功时，pMap指向的就是内核中文件缓冲区
    char * pMap = mmap(NULL, filelength, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    int ret = send(peerfd, pMap, filelength, 0);
    printf("send ret: %d\n", ret);

    return 0;
}
#endif
/*}}}*/
